Skip to content

boolean-contains: incorrectly returns true for Polygon with bite taken out of the side and the polygon's boundary box#1897

Closed
mglasgow wants to merge 1 commit into
Turfjs:masterfrom
mglasgow:fix-contains-bug
Closed

boolean-contains: incorrectly returns true for Polygon with bite taken out of the side and the polygon's boundary box#1897
mglasgow wants to merge 1 commit into
Turfjs:masterfrom
mglasgow:fix-contains-bug

Conversation

@mglasgow

@mglasgow mglasgow commented May 5, 2020

Copy link
Copy Markdown
  • Use a meaningful title for the pull request. Include the name of the package modified.
  • Have read How To Contribute.
  • Run npm test at the sub modules where changes have occurred.
  • Run npm run lint to ensure code style at the turf module level.

When using Turfjs to generate quadtiles from OpenStreetMap country boundaries, I ran into scenario where boolean-contains returns true when it should not have. The polygons in question are:

screenshot

I am checking if the polygon with the "bite" taken out the side contains the rectangular tile that clipped it. boolean-contains returns true, when it should return false.

The current logic does a check that all the points of the contained polygon lie within the containing polygon. This is obviously a good short-cut test to perform, but cannot give a definitive yes answer.

I looked at the JTS implementation of cover() to get an idea of how they are doing it. They have some point-in-poly shortcut tests, then have to do a full topological comparison. I used cover() rather than contains() because it matches the implementation of how turfjs has defined contains, in regards to polygon boundaries. The comments on JTS contains() vs compare() code do a good job outlining the difference.

My PR to fix this adds another check to the logic. Only return true if the difference between the polygon being contained and the containing polygon is null. IE, check that there is no part of the contained polygon outside of the containing polygon.

I have added equivalent logic to boolean-within, as well as test cases to both.

On the topic of boolean-within, the logic is slightly different to boolean-contains, specifically it's handling of features with null geometries. If this is meant to be an exact opposite of boolean-contains, perhaps it can simply import the boolean-contains package and call it with swapped parameters, rather than duplicating the code in both packages?

…out the side (but not the corner) and the polygon boundary, the function currently returns true. It should return false.

same logic appled to boolean-within
@rowanwins

Copy link
Copy Markdown
Member

Hey @mglasgow

Thanks for the thoughtful PR - these boolean modules probably need a bit of rework in general. Ideally I think we want to avoid having to do a difference operation because that's quite expensive and these boolean modules are supposed to be fast - that said I probably broke that rule because I didn't know much about algorithms when I wrote them :)

Given the changes at this stage are relatively minor I'm probably happy to merge and tackle a bigger overhaul down the track, but I'd like a few days to ponder just in case anything comes to mind

Cheers
Rowan

PS Greetings from Canberra!

@mglasgow

mglasgow commented May 5, 2020

Copy link
Copy Markdown
Author

Hi Rowan, I am up in Sydney!

I did give some thought to the implementation. I agree, difference is definitely a heavier operation than the current point-in-poly comparisons. I erred on the side of correctness over performance in this instance. It would be nice if the Martinez implementation had a "short-circuiting" ability: return true or false if any of the polygons have a difference, rather than computing the entire geometry (which we throw away).

The other option I considered was to check that none of the containing polygon points in the boundary lie in the interior (but not exterior) of the polygon that is to be contained. This seems to be going down the path of the segment intersection logic that JTS implements in it's PreparedGeometry implementation.

The simpler solution for my needs was definitely using a difference operator.

I've just considered another general optimisation that could be made in regards to the bounding boxes: rather than checking for overlap, the function could check that the bounding box is contained as well?

@rowanwins

Copy link
Copy Markdown
Member

I wonder if we could use something like https://github.com/rowanwins/shamos-hoey or https://github.com/rowanwins/sweepline-intersections ?

@mfedderly

Copy link
Copy Markdown
Collaborator

I believe we've addressed some of this recently #3104 and there are plans to actually use @turf/difference after it gets moved to a much faster implementation in v8 (#3109).

@mfedderly mfedderly closed this Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants